package com.leinao.git;

import com.jcraft.jsch.Session;
import com.leinao.config.EnvironmentConfig;
import com.leinao.constant.Constant;
import com.leinao.file.dto.FileInfoDto;
import com.leinao.util.SSHUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.archive.TarFormat;
import org.eclipse.jgit.archive.TgzFormat;
import org.eclipse.jgit.archive.ZipFormat;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.lib.*;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.*;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.FS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 *
 * @Description 文件下载控制类
 * @author lizi
 * @date 2019/05/22 下午6:19
 * @Version 1.0
 */
@Component
public class JGitService {
    private static final Logger LOGGER = LoggerFactory.getLogger(JGitService.class);

    // git操作超时产生的锁定文件
    private static final String GIT_LOCK_FILE = "/.git/index.lock";

    /**
     * "zip","tar","tar.gz"
     */
    public interface ArchiveFormatName {
        String zip = "zip";
        String tar = "tar";
        String tar_gz = "tar.gz";
    }

    static {
        ArchiveCommand.registerFormat(ArchiveFormatName.tar_gz, new TgzFormat());
        ArchiveCommand.registerFormat(ArchiveFormatName.tar, new TarFormat());
        ArchiveCommand.registerFormat(ArchiveFormatName.zip, new ZipFormat());
    }

    @Autowired
    private EnvironmentConfig env;

    // git 认证方式
    private GitAuthTypeEnum authTypeEnum = GitAuthTypeEnum.SSH;
    private String ip;
    private String port;
    private String username;
    private String password;
    private String path;
    // git仓库地址前缀
    private String repoPrefix;
    private static final String GIT_PROJECT_POSTFIX = ".git";

    // git认证信息
    private UsernamePasswordCredentialsProvider provider = null;

    // ssh秘钥问题：https://www.codeaffine.com/2014/12/09/jgit-authentication/
    private TransportConfigCallback transportCallback = null;

    /**
     * Bean初始化时候的执行顺序： 构造方法 -> @Autowired -> @PostConstruct
     */
    @PostConstruct
    public void init() {
        // 获取git配置信息
        ip = env.getGitIP();
        port = env.getGitPort();
        username = env.getGitUsername();
        password = env.getGitPassword();
        path = env.getGitPath();
        int authType = env.getGitAuthType();

        authTypeEnum = GitAuthTypeEnum.getGitAuthTypeByType(authType);
        if(GitAuthTypeEnum.HTTP == authTypeEnum) {
            repoPrefix = authTypeEnum.getProtocol() + ip;

            // 认证信息初始化
            provider = new UsernamePasswordCredentialsProvider(username, password);
        } else if(GitAuthTypeEnum.SSH == authTypeEnum) {
            repoPrefix = authTypeEnum.getProtocol() + username + "@" + ip;

            // 认证信息初始化
            transportCallback = (transport) -> {
                SshTransport sshTransport = (SshTransport) transport;
                sshTransport.setSshSessionFactory(new JschConfigSessionFactory() {
                    // https://www.programcreek.com/java-api-examples/?api=org.eclipse.jgit.transport.CredentialsProviderUserInfo
                    @Override
                    protected void configure(OpenSshConfig.Host hc, Session session) {
                        session.setPassword(password);
                        session.setConfig("StrictHostKeyChecking", "no");
                    }
                });
            };
        }

        if(StringUtils.isNotEmpty(port)) {
            repoPrefix += ":" + port;
        }
    }

    /**
     * @Description 创建远程仓库
     *
     * @param projectPath 仓库地址 ${userId}/${projectName}-${date}
     */
    public String createRemoteRepository(String projectPath) {

        // 移除路径第一个斜线
        projectPath = projectPath.startsWith(File.separator) ? projectPath.substring(File.separator.length(), projectPath.length()) : projectPath;
        // 路径拼接.git字符串
        projectPath = projectPath.endsWith(GIT_PROJECT_POSTFIX) ? projectPath : projectPath + GIT_PROJECT_POSTFIX;

        String result = null;
        String projectFullPath = path + projectPath;
        String command = "mkdir -p " + projectFullPath + " && git init --bare " + projectFullPath;
        try {
            if(StringUtils.isNotEmpty(port)) {
                // Initialized empty Git repository in /opt/data/_dc0eca94ae81410299dd5f67fb1538a6/test-1558885938373.git/
                // Reinitialized existing Git repository in /opt/data/_91b9cf71a53447658933ddf2037c7c2a2/test-1231231231231.git/
                result = SSHUtils.execCommandByJSch(ip, Integer.valueOf(port), username, password, command);
            } else {
                // Initialized empty Git repository in /opt/data/_dc0eca94ae81410299dd5f67fb1538a6/test-1558885938373.git/
                // Reinitialized existing Git repository in /opt/data/_91b9cf71a53447658933ddf2037c7c2a2/test-1231231231231.git/
                result = SSHUtils.execCommandByJSch(ip, username, password, command);
            }
            LOGGER.info("createRemoteRepository param projectPath : {}, result : {}", projectPath, result);
            if(result.contains("Reinitialized existing Git repository") || result.contains("Initialized empty Git repository")) {
                result = repoPrefix +  projectFullPath;
            } else {
                result = null;
            }
        } catch (Exception e) {
            LOGGER.error("createRemoteRepository failed param projectPath : {}, exception : {}", projectPath, e);
        }

        return result;
    }

    /**
     * @Description 添加远程仓库
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param repository  git@github.com:${userId}/project1-123123423423423.git
     */
    public boolean addRemoteRepository(String localPath, String repository) {
        boolean addRemoteRepositoryFlag = true;
        try {
            Git.open(new File(localPath, GIT_PROJECT_POSTFIX))
                    .remoteAdd()
                    .setUri(new URIish(repository))
                    .setName("origin").call();
            LOGGER.info("addRemoteRepository param localPath : {}, repository : {}", localPath, repository);
        } catch (Exception e) {
            addRemoteRepositoryFlag = false;
            LOGGER.error("addRemoteRepository failed param localPath : {}, repository : {}, exception : {}", localPath, repository, e);
        }
        return addRemoteRepositoryFlag;
    }

    /**
     * @Description 移除远程仓库
     *
     * @param localPath /projects/${userId}/${projectName}
     */
    public boolean removeRemoteRepository(String localPath) {
        boolean removeRemoteRepositoryFlag = true;
        try {
            RemoteRemoveCommand remoteRemoveCommand = Git.open(new File(localPath, GIT_PROJECT_POSTFIX)).remoteRemove();
            remoteRemoveCommand.setName("origin");
            remoteRemoveCommand.call();
            LOGGER.info("removeRemoteRepository param localPath : {}", localPath);
        } catch (Exception e) {
            removeRemoteRepositoryFlag = false;
            LOGGER.error("removeRemoteRepository failed param localPath : {}, exception : {}", localPath, e);
        }
        return removeRemoteRepositoryFlag;
    }

    /**
     * @Description 创建本地仓库
     *
     * @param localPath 仓库地址 /projects/${userId}/${projectName}
     */
    public boolean init(String localPath) {
        boolean initFlag = true;
        try {
            Repository repository = FileRepositoryBuilder.create(new File(localPath, GIT_PROJECT_POSTFIX));
            repository.create();
            LOGGER.info("init param repositoryPath : {}", localPath);
        } catch (IllegalStateException e) {
            if(e.getMessage() != null && e.getMessage().contains("Repository already exists")) {
                LOGGER.info("init localPath exists : {}, message : {}", localPath, e.getMessage());
            } else {
                initFlag = false;
                LOGGER.error("init failed param localPath : {}, exception : {}", localPath, e);
            }
        } catch (Exception e) {
            initFlag = false;
            LOGGER.error("init failed param localPath : {}, exception : {}", localPath, e);
        }
        return initFlag;
    }

    /**
     * @Description 克隆仓库
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param repository  git@github.com:${userId}/project1-123123423423423.git
     */
    public boolean clone(String localPath, String repository) {
        boolean cloneFlag = true;
        try {
            CloneCommand cloneCommand = Git.cloneRepository().setURI(repository) //设置远程URI
                    .setDirectory(new File(localPath));  //设置下载存放路径

            // 初始化认证信息
            initGitAuth(cloneCommand);
            cloneCommand.call();
            LOGGER.info("clone param localPath : {}, repository : {}", localPath, repository);
        } catch (JGitInternalException e) {
            if(e.getMessage() != null && e.getMessage().contains("already exists and is not an empty directory")) {
                LOGGER.info("clone localPath {} exists repository : {}, message : {}", localPath, repository, e.getMessage());
            } else {
                cloneFlag = false;
                LOGGER.error("clone failed param localPath : {}, repository : {}, exception : {}", localPath, repository, e);
            }
        } catch (Exception e) {
            cloneFlag = false;
            LOGGER.error("clone failed param localPath : {}, repository : {}, exception : {}", localPath, repository, e);
        }
        return cloneFlag;
    }

    /**
     * @Description 从远程获取最新版本到本地   不会自动合并 merge
     *
     * @param localPath /projects/${userId}/${projectName}
     */
    public boolean fetch(String localPath){
        boolean fetchFlag = true;
        try ( Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX))) {
            FetchCommand fetchCommand = git.fetch().setCheckFetchedObjects(true);

            // 初始化认证信息
            initGitAuth(fetchCommand);
            fetchCommand.call();
            LOGGER.info("fetch repository at {}", git.getRepository().getDirectory());
        } catch (Exception e) {
            fetchFlag = false;
            LOGGER.error("fetch repository failed param localPath : {}, exception : {}", localPath, e);
        }
        return fetchFlag;
    }

    /**
     * @Description 添加文件
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param fileName /projects/${userId}/${projectName}/code/123.txt or code/123.txt or code/*
     */
    public boolean add(String localPath, String fileName) {
        boolean addFlag = true;
        try (Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX))) {
            git.add().addFilepattern(fileName).call();
            git.add().addFilepattern(fileName).setUpdate(true).call();
            LOGGER.info("add file {} to repository at {}", fileName, git.getRepository().getDirectory());
        } catch (Exception e) {
            addFlag = false;
            LOGGER.error("add failed param fileName : {}, localPath : {}, exception : {}", fileName, localPath, e);
        }
        return addFlag;
    }

    /**
     * @Description 提交代码到本地仓库
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param comment 提交git内容描述
     */
    public boolean commit(String localPath, String comment) {
        boolean commitFlag = true;
        try (Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX))) {
            //提交代码到本地仓库
            git.commit().setMessage(comment).call();
            LOGGER.info("commit to repository at {}", git.getRepository().getDirectory());
        } catch (Exception e) {
            commitFlag = false;
            LOGGER.error("commit failed param localPath : {}, comment : {}, exception : {}", localPath, comment, e);
        }
        return commitFlag;
    }

    /**
     * @Description 提交代码到远程仓库
     *
     * @param localPath /projects/${userId}/${projectName}
     */
    public boolean push(String localPath) {
        boolean pushFlag = true;
        try (Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX))) {
            PushCommand pushCommand = git.push().setForce(true);

            // 初始化认证信息
            initGitAuth(pushCommand);
            pushCommand.call();
            LOGGER.info("push {}", git.getRepository() + File.separator + git.getRepository().getBranch());
        } catch (Exception e) {
            pushFlag = false;
            LOGGER.error("push failed param localPath : {}, exception : {}", localPath, e);
        }
        return pushFlag;
    }

    /**
     * @Description 提交并推送代码至远程服务器
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param filePath /projects/${userId}/${projectName}/code/123.txt or code/123.txt or code/*
     * @param comment 提交描述
     * @param retry 是否进行重试
     */
    public boolean commitAndPush(String localPath, String filePath, String comment, boolean retry){
        boolean commitAndPushFlag = true;
        try (Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX))) {
            // 添加
            long now = System.currentTimeMillis();
            git.add().addFilepattern(filePath).call();
            LOGGER.info("commitAndPush add localPath : {}, spend time : {}s：", localPath, (System.currentTimeMillis()  - now) / 1000);

            now = System.currentTimeMillis();
            git.add().addFilepattern(filePath).setUpdate(true).call();
            LOGGER.info("commitAndPush add setUpdate localPath : {}, spend time : {}s：", localPath, (System.currentTimeMillis()  - now) / 1000);

            //提交
            now = System.currentTimeMillis();
            git.commit().setMessage(comment).call();
            LOGGER.info("commitAndPush commit localPath : {}, spend time : {}s：", localPath, (System.currentTimeMillis()  - now) / 1000);

            //推送到远程
            now = System.currentTimeMillis();
            PushCommand pushCommand = git.push();
            LOGGER.info("commitAndPush push localPath : {}, spend time : {}s：", localPath, (System.currentTimeMillis()  - now) / 1000);

            // 初始化认证信息
            initGitAuth(pushCommand);

            now = System.currentTimeMillis();
            pushCommand.call();
            LOGGER.info("commitAndPush push call localPath : {}, spend time : {}s：", localPath, (System.currentTimeMillis()  - now) / 1000);
            LOGGER.info("commitAndPush file {} to repository at {}", filePath, git.getRepository().getDirectory());
        } catch (LockFailedException e) {
            commitAndPushFlag = false;
            // 有时候git提交文件超时，会产生锁定文件，需要删除重试
            if(retry) {
                Path gitLockFilePath = Paths.get(localPath + GIT_LOCK_FILE);
                try {
                    boolean existLockFile = Files.exists(gitLockFilePath);
                    LOGGER.info("commitAndPush delete gitLockFile gitLockFilePath : {}, existLockFile : {}", gitLockFilePath, existLockFile);
                    if(existLockFile) {
                        Files.delete(gitLockFilePath);
                        commitAndPushFlag = commitAndPush(localPath, filePath, comment, false);
                    }
                } catch (Exception ex) {
                    LOGGER.error("commitAndPush delete gitLockFile failed param gitLockFilePath : {}, exception : {}", gitLockFilePath, e);
                }
            }
        } catch (Exception e) {
            commitAndPushFlag = false;
            LOGGER.error("commitAndPush failed param localPath {}, filePath : {}, comment : {}, exception : {}", localPath, filePath, comment, e);
        }
        return commitAndPushFlag;
    }

    /**
     * @Description 拉取远程代码
     *
     * @param localPath /projects/${userId}/${projectName}
     */
    public boolean pull(String localPath){
        return pull(localPath, "master");
    }

    /**
     * @Description 拉取远程某个分支代码
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param remoteBranchName 远程分支名
     */
    public boolean pull(String localPath, String remoteBranchName){
        boolean pullFlag = true;
        try (Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX))) {
            PullCommand pushCommand = git.pull().setRemoteBranchName(remoteBranchName);

            // 初始化认证信息
            initGitAuth(pushCommand);
            pushCommand.call();
            LOGGER.info("pull branch {} to repository at {}", remoteBranchName, git.getRepository().getDirectory());
        } catch (Exception e) {
            pullFlag = false;
            LOGGER.error("pull failed param localPath : {}, remoteBranchName : {}, exception : {}", localPath, remoteBranchName, e);
        }
        return pullFlag;
    }

    /**
     * @Description 切换分支
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param branchName 分支名
     */
    public boolean checkout(String localPath, String branchName){
        boolean checkoutFlag = true;
        try (Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX))) {
            git.checkout().setName("refs/heads/" + branchName).setForce(true).call();
        } catch (Exception e) {
            checkoutFlag = false;
            LOGGER.error("checkout failed param localPath : {}, branchName : {}, exception : {}", localPath, branchName, e);
        }
        return checkoutFlag;
    }

    /**
     * @Description 根据主干master新建分支并同步到远程仓库
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param branchName 分支名
     */
    public boolean createBranch(String localPath, String branchName) {
        boolean createBranchFlag = true;
        try {
            Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX));
            String newBranchIndex = "refs/heads/" + branchName;
            //检查新建的分支是否已经存在，如果存在则忽略
            List<Ref> refs = git.branchList().call();
            for (Ref ref : refs) {
                if (ref.getName().equals(newBranchIndex)) {
                    LOGGER.info("createBranch Exist branch {} before", branchName);
                    return true;
                }
            }

            //新建分支
            Ref ref = git.branchCreate().setName(branchName).call();

            //推送分支到远程
            PushCommand pushCommand = git.push().add(ref);

            // 初始化认证信息
            initGitAuth(pushCommand);
            pushCommand.call();
        } catch (Exception e) {
            createBranchFlag = false;
            LOGGER.error("createBranch failed param localPath : {}, branchName : {}, exception : {}", localPath, branchName, e);
        }
        return createBranchFlag;
    }

    /**
     * @Description 删除指定分支
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param branchName 分支名
     */
    public boolean deleteBranch(String localPath, String branchName) {
        boolean deleteBranchFlag = true;
        try {
            Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX));
            String newBranchIndex = "refs/heads/" + branchName;
            //检查删除的分支是否已经存在，如果存在则强制删除
            List<Ref> refs = git.branchList().call();
            for (Ref ref : refs) {
                if (ref.getName().equals(newBranchIndex)) {
                    git.branchDelete().setBranchNames(branchName).setForce(true).call();

                    //推送删除分支到远程
                    PushCommand pushCommand = git.push().add(ref);

                    // 初始化认证信息
                    initGitAuth(pushCommand);
                    pushCommand.call();
                    LOGGER.info("deleteBranch Removing branch {}", branchName);
                    break;
                }
            }
        } catch (Exception e) {
            deleteBranchFlag = false;
            LOGGER.error("deleteBranch failed param localPath : {}, branchName : {}, exception : {}", localPath, branchName, e);
        }
        return deleteBranchFlag;
    }

    /**
     * @Description 保留指定的分支集合
     *
     * @param localPath /projects/${userId}/${projectName}
     */
    public boolean keepBranch(String localPath, List<String> branchNameList) {
        boolean keepBranchFlag = true;
        try {
            Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX));

            // 完成路径名称的分支集合
            Set<String> refBranchNameSet = new HashSet<>();
            branchNameList.forEach(branchName -> {
                refBranchNameSet.add( "refs/heads/" + branchName);
            });

            //检查分支是否已经存在，如果存在则保留并推送，否则进行删除
            List<Ref> refs = git.branchList().call();
            for (Ref ref : refs) {
                if (!refBranchNameSet.contains(ref.getName())) {
                    git.branchDelete().setBranchNames(ref.getName()).setForce(true).call();
                    LOGGER.info("keepBranch Removing branch {}", ref.getName());
                } else {
                    //推送分支到远程
                    PushCommand pushCommand = git.push().add(ref);

                    // 初始化认证信息
                    initGitAuth(pushCommand);
                    pushCommand.call();
                }
            }
        } catch (Exception e) {
            keepBranchFlag = false;
            LOGGER.error("keepBranch failed param localPath : {}, exception : {}", localPath, e);
        }
        return keepBranchFlag;
    }

    /**
     * @Description 根据主干master新建Tag并同步到远程仓库
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param tagName 标签名
     */
    public boolean createTag(String localPath, String tagName) {
        boolean createTagFlag = true;
        try {
            Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX));
            String newTagIndex = "refs/tags/" + tagName;
            //检查新建的Tag是否已经存在，如果存在则忽略
            List<Ref> refs = git.tagList().call();
            for (Ref ref : refs) {
                if (ref.getName().equals(newTagIndex)) {
                    LOGGER.info("createTag Exist tag {} before", tagName);
                    return true;
                }
            }

            //新建Tag
            Ref ref = git.tag().setName(tagName).setAnnotated(true).call();

            //推送分支到远程
            PushCommand pushCommand = git.push().add(ref);

            // 初始化认证信息
            initGitAuth(pushCommand);
            pushCommand.call();
        } catch (Exception e) {
            createTagFlag = false;
            LOGGER.error("createTag failed param localPath : {}, tagName : {}, exception : {}", localPath, tagName, e);
        }
        return createTagFlag;
    }

    /**
     * @Description 删除指定标签
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param tagName 标签名
     */
    public boolean deleteTag(String localPath, String tagName) {
        boolean deleteTagFlag = true;
        try {
            Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX));
            String newBranchIndex = "refs/tags/" + tagName;
            //检查删除的标签是否已经存在，如果存在则强制删除
            List<Ref> refs = git.branchList().call();
            for (Ref ref : refs) {
                if (ref.getName().equals(newBranchIndex)) {
                    git.tagDelete().setTags(tagName).call();

                    //推送删除分支到远程
                    PushCommand pushCommand = git.push().add(ref);

                    // 初始化认证信息
                    initGitAuth(pushCommand);
                    pushCommand.call();
                    LOGGER.info("deleteTag Removing tag {}", tagName);
                    break;
                }
            }
        } catch (Exception e) {
            deleteTagFlag = false;
            LOGGER.error("deleteTag failed param localPath : {}, tagName : {}, exception : {}", localPath, tagName, e);
        }
        return deleteTagFlag;
    }

    /**
     * @Description 保留指定的标签集合
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param tagNameList 标签集合
     */
    public boolean keepTag(String localPath, List<String> tagNameList) {
        boolean keepTagFlag = true;
        try {
            Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX));

            // 完成路径名称的标签集合
            Set<String> refTagNameSet = new HashSet<>();
            tagNameList.forEach(tagName -> {
                refTagNameSet.add( "refs/tags/" + tagName);
            });

            //检查Tag是否已经存在，如果存在则保留并推送，否则进行删除
            List<Ref> refs = git.tagList().call();
            for (Ref ref : refs) {
                if (!refTagNameSet.contains(ref.getName())) {
                    git.tagDelete().setTags(ref.getName()).call();
                    LOGGER.info("keepTag Removing tag {}", ref.getName());
                } else {
                    //推送分支到远程
                    PushCommand pushCommand = git.push().add(ref);

                    // 初始化认证信息
                    initGitAuth(pushCommand);
                    pushCommand.call();
                }
            }
        } catch (Exception e) {
            keepTagFlag = false;
            LOGGER.error("keepTag failed param localPath : {}, tagNameList : {}, exception : {}", localPath, tagNameList, e);
        }
        return keepTagFlag;
    }

    /**
     * @Description master分支使用指定标签代码
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param tagName 标签名
     */
    public boolean resetTag(String localPath, String tagName){
        boolean resetTagFlag = true;
        try (Git git = Git.open(new File(localPath, GIT_PROJECT_POSTFIX))) {
            git.reset().disableRefLog(true).setMode(ResetCommand.ResetType.HARD).setRef("refs/tags/" + tagName).call();
        } catch (Exception e) {
            resetTagFlag = false;
            LOGGER.error("resetTag failed param localPath : {}, tagName : {}, exception : {}", localPath, tagName, e);
        }
        return resetTagFlag;
    }

    /**
     * @Description 遍历远程仓库某个分支下的所有文件
     *
     * @param repository 远程仓库地址 git@github.com:${userId}/project1-123123423423423.git
     * @param tagName 标签名
     * @param dir 搜索路径，空字符串表示树的根
     * @author suqiliang
     * @Date 2018年6月29日 上午11:56:52
     */
    public List<FileInfoDto> getRemoteFileLst(String repository, String tagName, String dir) {
        try {
            // 获取远程仓库信息
            Repository repo = getRemoteRepository(repository);

            // 获取仓库文件列表
            final ObjectId branch = repo.resolve("refs/remotes/origin/" + tagName);
            if(branch == null) {
                LOGGER.error("getRemoteFileLst repository : {}, tagName {} not existed", repository, tagName);
                return Collections.emptyList();
            }

            List<FileInfoDto> fileList = Lists.newArrayList();
            readElementsAt(repo, branch.getName(), dir, fileList);
            LOGGER.info("getRemoteFileLst Had paths for commit: {}", fileList.size());

            return fileList;
        } catch (Exception e) {
            LOGGER.error("getRemoteFileLst failed param repository : {}, tagName : {}, dir : {}, exception : {}", repository, tagName, dir, e);
            return Collections.emptyList();
        }
    }

    /**
     * @Description 遍历本地仓库某个分支下的所有文件
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param tagName 标签名
     * @param dir 搜索路径，空字符串表示树的根
     * @author suqiliang
     * @Date 2018年6月29日 上午11:56:52
     */
    public List<FileInfoDto> getLocalFileList(String localPath, String tagName, String dir) {
        try {
            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            Repository repository = builder
                    .readEnvironment() // scan environment GIT_* variables
                    .findGitDir(new File(localPath, GIT_PROJECT_POSTFIX)) // scan up the file system tree
                    .build();

            // 获取仓库文件列表
            final ObjectId branch = repository.resolve(tagName);
            if(branch == null) {
                LOGGER.error("getLocalFileList localPath : {}, tagName {} not existed", localPath, tagName);
                return Collections.emptyList();
            }

            List<FileInfoDto> fileList = Lists.newArrayList();
            readElementsAt(repository, branch.getName(), dir, fileList);
            LOGGER.info("getLocalFileList Had paths for commit: {}", fileList.size());

            return fileList;
        } catch (Exception e) {
            LOGGER.error("getLocalFileList failed param localPath : {}, tagName : {}, dir : {}, exception : {}", localPath, tagName, dir, e);
            return Collections.emptyList();
        }
    }

    /**
     * @Description 下载远程仓库某个分支下的单个文件
     *
     * @param repository 远程仓库地址 git@github.com:${userId}/project1-123123423423423.git
     * @param tagName 标签名
     * @param dir 搜索文件路径
     * @author suqiliang
     * @Date 2018年6月29日 上午11:56:52
     */
    public void downloadRemoteFile(String repository, String tagName, String dir, OutputStream outputStream) {
        try {
            // 获取远程仓库信息
            Repository repo = getRemoteRepository(repository);

            // 获取仓库文件列表
            final ObjectId branch = repo.resolve("refs/remotes/origin/" + tagName);
            if(branch == null) {
                LOGGER.error("downloadRemoteFile repository : {}, tagName {} not existed", repository, tagName);
                return;
            }

            RevWalk revWalk = new RevWalk(repo);
            RevCommit commit = revWalk.parseCommit(branch);
            RevTree tree = commit.getTree();
            TreeWalk treeWalk = new TreeWalk(repo);
            treeWalk.addTree(tree);
            treeWalk.setRecursive(true);
            treeWalk.setFilter(PathFilter.create(dir));
            if (!treeWalk.next()) {
                return;
            }
            ObjectId objectId = treeWalk.getObjectId(0);
            ObjectLoader objectLoader = repo.open(objectId);
            objectLoader.copyTo(outputStream);
        } catch (Exception e) {
            LOGGER.error("downloadRemoteFile failed param repository : {}, tagName : {}, dir : {}, exception : {}", repository, tagName, dir, e);
        }
    }

    /**
     * @Description 下载本地仓库某个分支下的单个文件
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param tagName 标签名
     * @param dir 搜索文件路径
     * @author suqiliang
     * @Date 2018年6月29日 上午11:56:52
     */
    public void downloadLocalFile(String localPath, String tagName, String dir, OutputStream outputStream) {
        try {
            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            Repository repository = builder
                    .readEnvironment() // scan environment GIT_* variables
                    .findGitDir(new File(localPath, GIT_PROJECT_POSTFIX)) // scan up the file system tree
                    .build();

            // 获取仓库文件列表
            final ObjectId branch = repository.resolve(tagName);
            if(branch == null) {
                LOGGER.error("downloadLocalFile localPath : {}, tagName {} not existed", localPath, tagName);
                return;
            }

            RevWalk revWalk = new RevWalk(repository);
            RevCommit commit = revWalk.parseCommit(branch);
            RevTree tree = commit.getTree();
            TreeWalk treeWalk = new TreeWalk(repository);
            treeWalk.addTree(tree);
            treeWalk.setRecursive(true);
            treeWalk.setFilter(PathFilter.create(dir));
            if (!treeWalk.next()) {
                return;
            }
            ObjectId objectId = treeWalk.getObjectId(0);
            ObjectLoader objectLoader = repository.open(objectId);
            objectLoader.copyTo(outputStream);
        } catch (Exception e) {
            LOGGER.error("downloadRemoteFile failed param localPath : {}, tagName : {}, dir : {}, exception : {}", localPath, tagName, dir, e);
        }
    }

    /**
     * @Description 批量下载远程仓库某个分支下的文件
     *
     * @param repository 远程仓库地址 git@github.com:${userId}/project1-123123423423423.git
     * @param tagName 标签名
     * @param dirs 搜索文件路径数组
     * @author suqiliang
     * @Date 2018年6月29日 上午11:56:52
     */
    public void batchDownloadRemoteFile(String repository, String tagName, String[] dirs, OutputStream outputStream) {
        try {
            // 获取远程仓库信息
            Repository repo = getRemoteRepository(repository);

            // 获取仓库文件列表
            final ObjectId branch = repo.resolve("refs/remotes/origin/" + tagName);
            if(branch == null) {
                LOGGER.error("batchDownloadFile repository : {}, tagName {} not existed", repository, tagName);
                return;
            }

            ArchiveCommand archive = Git.wrap(repo).archive().setOutputStream(outputStream).setTree(branch);
            archive.setFormat(ArchiveFormatName.zip);
            archive.setPaths(dirs);
            archive.call();
        } catch (Exception e) {
            LOGGER.error("downloadRemoteFile failed param repository : {}, tagName : {}, dirs : {}, exception : {}", repository, tagName, dirs, e);
        }
    }

    /**
     * @Description 批量下载本地仓库某个分支下的文件
     *
     * @param localPath /projects/${userId}/${projectName}
     * @param tagName 标签名
     * @param dirs 搜索文件路径数组
     * @author suqiliang
     * @Date 2018年6月29日 上午11:56:52
     */
    public void batchDownloadLocalFile(String localPath, String tagName, String[] dirs, OutputStream outputStream) {
        try {
            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            Repository repository = builder
                    .readEnvironment() // scan environment GIT_* variables
                    .findGitDir(new File(localPath, GIT_PROJECT_POSTFIX)) // scan up the file system tree
                    .build();

            // 获取仓库文件列表
            final ObjectId branch = repository.resolve(tagName);
            if(branch == null) {
                LOGGER.error("batchDownloadLocalFile localPath : {}, tagName {} not existed", localPath, tagName);
                return;
            }

            ArchiveCommand archive = Git.wrap(repository).archive().setOutputStream(outputStream).setTree(branch);
            archive.setFormat(ArchiveFormatName.zip);
            archive.setPaths(dirs);
            archive.call();
        } catch (Exception e) {
            LOGGER.error("downloadRemoteFile failed param localPath : {}, tagName : {}, dirs : {}, exception : {}", localPath, tagName, dirs, e);
        }
    }

    /**
     * @Description 判断远程仓库某个文件是否存在
     * @param repository 远程仓库地址 git@github.com:${userId}/project1-123123423423423.git
     * @param tagName 标签名
     * @param dir 搜索路径，空字符串表示树的根
     * @author lizi
     * @date 2018/12/18 下午6:43
     */
    public boolean existRemoteFile(String repository, String tagName, String dir) {
        boolean existRemoteFileFlag = true;
        try {
            // 获取远程仓库信息
            Repository repo = getRemoteRepository(repository);

            // 获取仓库文件列表
            final ObjectId branch = repo.resolve("refs/remotes/origin/" + tagName);
            if(branch == null) {
                LOGGER.error("existRemoteFile repository : {}, tagName {} not existed", repository, tagName);
                return false;
            }

            RevWalk revWalk = new RevWalk(repo);
            RevCommit commit = revWalk.parseCommit(branch);
            RevTree tree = commit.getTree();
            TreeWalk treeWalk = new TreeWalk(repo);
            treeWalk.addTree(tree);
            treeWalk.setRecursive(true);
            treeWalk.setFilter(PathFilter.create(dir));
            if (!treeWalk.next()) {
                existRemoteFileFlag = false;
            }
        } catch (Exception e) {
            existRemoteFileFlag = false;
            LOGGER.error("existRemoteFile failed param repository : {}, tagName : {}, dir : {}, exception : {}", repository, tagName, dir, e);
        }
        return existRemoteFileFlag;
    }

    /**
     * @Description 判断本地仓库某个文件是否存在
     * @param localPath /projects/${userId}/${projectName}
     * @param tagName 标签名
     * @param dir 搜索路径，空字符串表示树的根
     * @author lizi
     * @date 2018/12/18 下午6:43
     */
    public boolean existLocalFile(String localPath, String tagName, String dir) {
        boolean existRemoteFileFlag = true;
        try {
            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            Repository repository = builder
                    .readEnvironment() // scan environment GIT_* variables
                    .findGitDir(new File(localPath, GIT_PROJECT_POSTFIX)) // scan up the file system tree
                    .build();

            // 获取仓库文件列表
            final ObjectId branch = repository.resolve(tagName);
            if(branch == null) {
                LOGGER.error("existLocalFile localPath : {}, tagName {} not existed", localPath, tagName);
                return false;
            }

            RevWalk revWalk = new RevWalk(repository);
            RevCommit commit = revWalk.parseCommit(branch);
            RevTree tree = commit.getTree();
            TreeWalk treeWalk = new TreeWalk(repository);
            treeWalk.addTree(tree);
            treeWalk.setRecursive(true);
            treeWalk.setFilter(PathFilter.create(dir));
            if (!treeWalk.next()) {
                existRemoteFileFlag = false;
            }
        } catch (Exception e) {
            existRemoteFileFlag = false;
            LOGGER.error("existRemoteFile failed param localPath : {}, tagName : {}, dir : {}, exception : {}", localPath, tagName, dir, e);
        }
        return existRemoteFileFlag;
    }

    /**
     * @Description 判断远程仓库某个文件是否是文件夹
     * @param repository 远程仓库地址 git@github.com:${userId}/project1-123123423423423.git
     * @param tagName 标签名
     * @param dir 搜索路径，空字符串表示树的根
     * @author lizi
     * @date 2018/12/18 下午6:43
     */
    public boolean isRemoteDirectory(String repository, String tagName, String dir) {
        boolean isDirectoryFlag = true;
        try {
            // 获取远程仓库信息
            Repository repo = getRemoteRepository(repository);

            // 获取仓库文件列表
            final ObjectId branch = repo.resolve("refs/remotes/origin/" + tagName);
            if(branch == null) {
                LOGGER.error("isDirectory repository : {}, tagName {} not existed", repository, tagName);
                return false;
            }

            RevWalk revWalk = new RevWalk(repo);
            RevCommit commit = revWalk.parseCommit(branch);
            RevTree tree = commit.getTree();
            TreeWalk treeWalk = TreeWalk.forPath(repo, dir, tree);
            if((treeWalk.getFileMode(0).getBits() & FileMode.TYPE_TREE) == 0) {
                isDirectoryFlag = false;
            }
        } catch (Exception e) {
            isDirectoryFlag = false;
            LOGGER.error("isDirectory failed param repository : {}, tagName : {}, dir : {}, exception : {}", repository, tagName, dir, e);
        }
        return isDirectoryFlag;
    }

    /**
     * @Description 判断远程仓库某个文件是否是文件夹
     * @param localPath /projects/${userId}/${projectName}
     * @param tagName 标签名
     * @param dir 搜索路径，空字符串表示树的根
     * @author lizi
     * @date 2018/12/18 下午6:43
     */
    public boolean isLocalDirectory(String localPath, String tagName, String dir) {
        boolean isDirectoryFlag = true;
        try {
            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            Repository repository = builder
                    .readEnvironment() // scan environment GIT_* variables
                    .findGitDir(new File(localPath, GIT_PROJECT_POSTFIX)) // scan up the file system tree
                    .build();

            // 获取仓库文件列表
            final ObjectId branch = repository.resolve(tagName);
            if(branch == null) {
                LOGGER.error("existLocalFile localPath : {}, tagName {} not existed", localPath, tagName);
                return false;
            }

            RevWalk revWalk = new RevWalk(repository);
            RevCommit commit = revWalk.parseCommit(branch);
            RevTree tree = commit.getTree();
            TreeWalk treeWalk = TreeWalk.forPath(repository, dir, tree);
            if((treeWalk.getFileMode(0).getBits() & FileMode.TYPE_TREE) == 0) {
                isDirectoryFlag = false;
            }
        } catch (Exception e) {
            isDirectoryFlag = false;
            LOGGER.error("isDirectory failed param localPath : {}, tagName : {}, dir : {}, exception : {}", localPath, tagName, dir, e);
        }
        return isDirectoryFlag;
    }

    /**
     * @Description 获取远程仓库信息
     *
     * @param repository 远程仓库地址 git@github.com:${userId}/project1-123123423423423.git
     * @author suqiliang
     * @Date 2018年6月29日 上午11:56:52
     */
    private Repository getRemoteRepository(String repository) throws Exception {
        DfsRepositoryDescription desc =  new DfsRepositoryDescription();
        InMemoryRepository repo = new InMemoryRepository.Builder()
                .setRepositoryDescription(desc)
                // SshTransport depends on a real FS to read ~/.ssh/config, but
                // InMemoryRepository by default uses a null FS.
                // (dborowitz): Remove when we no longer depend on SSH.
                .setFS(FS.detect())
                .build();
        Config cfg = repo.getConfig();
        cfg.setString("remote", "origin", "url", repository);
        cfg.setString("remote", "origin", "fetch","+refs/heads/*:refs/remotes/origin/*");
        cfg.setString("remote", "origin", "fetch","+refs/tags/*:refs/remotes/origin/*");

        Git git = Git.wrap(repo);
        FetchCommand fetchCommand = git.fetch().setRemote("origin");

        // 初始化认证信息
        initGitAuth(fetchCommand);
        fetchCommand.call();
        return repo;
    }

    /**
     * @Description 获取git仓库文件列表
     *
     * @param repository 仓库地址
     * @param commit 提交历史记录点
     * @param dir 搜索路径，空字符串表示树的根
     * @param fileList 文件列表
     * @throws IOException
     */
    private void readElementsAt(Repository repository, String commit, String dir, List<FileInfoDto> fileList) throws IOException {
        RevCommit revCommit = buildRevCommit(repository, commit);

        // and using commit's tree find the path
        RevTree tree = revCommit.getTree();
        //System.out.println("Having tree: " + tree + " for commit " + commit);

        // shortcut for root-path
        if(StringUtils.isEmpty(dir)) {
            try (TreeWalk treeWalk = new TreeWalk(repository)) {
                treeWalk.addTree(tree);
                treeWalk.setRecursive(false);
                treeWalk.setPostOrderTraversal(false);

                while(treeWalk.next()) {
                    treeWalkToFileInfo(treeWalk ,fileList);
                }
            }
        } else {
            // now try to find a specific file
            try (TreeWalk treeWalk = buildTreeWalk(repository, tree, dir)) {
                if((treeWalk.getFileMode(0).getBits() & FileMode.TYPE_TREE) == 0) {
                    throw new IllegalStateException("Tried to read the elements of a non-tree for commit '" + commit + "' and dir '" + dir + "', had filemode " + treeWalk.getFileMode(0).getBits());
                }

                try (TreeWalk dirWalk = new TreeWalk(repository)) {
                    dirWalk.addTree(treeWalk.getObjectId(0));
                    dirWalk.setRecursive(false);
                    while(dirWalk.next()) {
                        treeWalkToFileInfo(dirWalk ,fileList);
                    }
                }
            }
        }
    }

    /**
     * @Description git节点树转换
     *
     * @param treeWalk git树节点信息
     * @param fileList 需要转换返回的文件列表
     */
    private void treeWalkToFileInfo(TreeWalk treeWalk, List<FileInfoDto> fileList) throws IOException {
        String filePath = treeWalk.getPathString();
        // 过滤.gitkeep文件
        if(StringUtils.isEmpty(filePath) || filePath.equals(Constant.HANDLE_GIT_EMPTY_DIR)) {
            return;
        }
        FileInfoDto fileInfoDto = new FileInfoDto();
        fileInfoDto.setFileName(filePath);
        fileInfoDto.setSymbolicLink(false);
        if((treeWalk.getFileMode(0).getBits() & FileMode.TYPE_TREE) == 0) {
            fileInfoDto.setDirectory(false);
            ObjectReader objectReader = treeWalk.getObjectReader();
            long size = objectReader.getObjectSize(treeWalk.getObjectId(0), 0);
            objectReader.close();
            fileInfoDto.setSize(formatFileSize(size));
        } else {
            fileInfoDto.setDirectory(true);
        }
        fileList.add(fileInfoDto);
    }

    /**
     * @Description 构建提交对象
     *
     * @param repository 仓库信息
     * @param commit 提交ID
     */
    private RevCommit buildRevCommit(Repository repository, String commit) throws IOException {
        // a RevWalk allows to walk over commits based on some filtering that is defined
        try (RevWalk revWalk = new RevWalk(repository)) {
            return revWalk.parseCommit(ObjectId.fromString(commit));
        }
    }

    /**
     * @Description 构建git树
     *
     * @param repository 代码仓库
     * @param tree git树
     * @param dir 代码位置
     */
    private TreeWalk buildTreeWalk(Repository repository, RevTree tree, final String dir) throws IOException {
        TreeWalk treeWalk = TreeWalk.forPath(repository, dir, tree);
        if(treeWalk == null) {
            throw new FileNotFoundException("Did not find expected file '" + dir + "' in tree '" + tree.getName() + "'");
        }

        return treeWalk;
    }

    /**
     * @Description 初始化认证信息
     *
     * @param command git命令
     */
    private void initGitAuth(TransportCommand command) {
        // 初始化认证信息
        if(GitAuthTypeEnum.HTTP == authTypeEnum) {
            command.setCredentialsProvider(provider);
        } else {
            command.setTransportConfigCallback(transportCallback);
        }
    }

    /**
     * @Description 文件大小格式化
     *
     * @param size : 文件大小
     * @author lizi
     * @Date 2018年7月2日 上午9:44:49
     */
    private String formatFileSize(long size) {
        DecimalFormat decimalFormat = new DecimalFormat("0.00");
        decimalFormat.setRoundingMode(RoundingMode.DOWN);
        if(size >= 1024 * 1024 * 1024) {
            return decimalFormat.format(size / 1024.0 / 1024.0 / 1024.0) + "G";
        } else if(size >= 1024 * 1024 && size < 1024 * 1024 * 1024) {
            return decimalFormat.format(size / 1024.0 / 1024.0) + "MB";
        } else if(size >= 1024 && size < 1024 * 1024) {
            return decimalFormat.format(size / 1024.0) + "KB";
        } else {
            return size + "B";
        }
    }
}
